require 'chord'
require 'documenttransferserver'
require 'persistentdb'
require 'documenttransferclient'
require 'apiserverlistener'

NUM_VNODES = 5
HOST = "172.16.89.1"
PORT = 1900
NUM_SUCCESSORS = 2

class UnrealDB
  def initialize
    @chord = Chord.new(NUM_VNODES, HOST, PORT, NUM_SUCCESSORS)
    @transfers_server = DocumentTransferServer.new(self)
    @api_listener = APIServerListener.new(self)
    @dbs = []
    @replicated_dbs = []
    @replication_succ_lock = Mutex.new
    @replication_pred_lock = Mutex.new
    @process_lock = Mutex.new
    @replication_connections = []
    @pred_host = {}
    @pred_id = {}
  end

  def create
    @process_lock.lock
    begin
      pre_internal_init
      @chord.create
      internal_init
    ensure
      @process_lock.unlock
    end    
  end

  def join(host)
    @process_lock.lock
    begin
      pre_internal_init
      @chord.join(host, PORT)
      internal_init
    ensure
      @process_lock.unlock
    end
  end

  def stop
    @dbs.each { |db| db.destroy }
    @replicated_dbs.each { |db| db.destroy }
    @transfers_server.stop_server
    @api_listener.stop_server
    @chord.finish
  end

  def get_local(key, node_id)
    #puts "[UnrealDB] Antes do lock do get_local."
    @process_lock.lock
    #puts "[UnrealDB] Depois do lock do get_local."

    begin
      @dbs.each do |db|
        if db.node_id == node_id
          return db.get_file(key)
        end
      end
      return nil
    ensure
      #puts "[UnrealDB] Lock liberado: get_local."
      @process_lock.unlock
    end
  end

  def put_local(document, document_id, node_id)
    replicate_to_conn = false

    #puts "[UnrealDB] Antes do lock do put_local."
    @process_lock.lock
    #puts "[UnrealDB] Depois do lock do put_local."

    begin
      @dbs.each do |db|
        if db.node_id == node_id
          return false if !db.insert_file(document_id, document)
          replicate_to_conn = true          
          return true
        end
      end
      @replicated_dbs.each do |db|
        if db.node_id == node_id
          puts "[UnrealDB] Insercao replicada!!!"
          return db.insert_file(document_id, document)
        end
      end
      puts "[UnrealDB] Nao e um banco local nem replicado!!!!"
      return false
    ensure
      #puts "[UnrealDB] Lock liberado: put_local."
      @process_lock.unlock
    end

    puts "[UnrealDB] Replicando para conexoes..."
    if replicate_to_conn
      @replication_connections.each do |conn|
        if !conn.insert_document(document_id, document, node_id)
          puts "[UnrealDB] Erro ao replicar. Talvez esse banco nao esteja sendo replicado."
        end
      end
    end
  end

  def update_local(document, document_id, node_id)
    replicate_to_conn = false

    @process_lock.lock

    begin
      @dbs.each do |db|
        if db.node_id == node_id
          return false if !db.update_file(document_id, document)
          replicate_to_conn = true          
          return true
        end
      end
      @replicated_dbs.each do |db|
        if db.node_id == node_id
          return db.update_file(document_id, document)
        end
      end
      return false
    ensure
      @process_lock.unlock
    end

    if replicate_to_conn
      @replication_connections.each do |conn|
        conn.update_document(document_id, document, node_id)
      end
    end
  end

  def remove_local(key, node_id)
    replicate_to_conn = false

    #puts "[UnrealDB] Antes do lock do remove_local."
    @process_lock.lock
    #puts "[UnrealDB] Depois do lock do remove_local."

    begin
      @dbs.each do |db|
        if db.node_id == node_id
          return false if !db.remove_file(key)
          replicate_to_conn = true          
          return true
        end
      end
      @replicated_dbs.each do |db|
        if db.node_id == node_id
          return db.remove_file(key)
        end
      end
      return nil
    ensure
      #puts "[UnrealDB] Lock liberado: remove_local."
      @process_lock.unlock
    end

    if replicate_to_conn
      @replication_connections.each do |conn|
        conn.remove_document(key, node_id)
      end
    end
  end

  def get_all_files_local(node_id)
    @process_lock.lock

    begin
      @dbs.each do |db|
        if db.node_id == node_id
          return db.get_all_files
        end
      end
      return nil
    ensure
      @process_lock.unlock
    end
  end

  def replace_all_files_local(node_id, zip_file_buff)
    #puts "[UnrealDB] Antes do lock do replace_all_files_local."
    @process_lock.lock
    #puts "[UnrealDB] Depois do lock do replace_all_files_local."

    begin
      @dbs.each do |db|
        if db.node_id == node_id
          return db.replace_all_files(zip_file_buff)
        end
      end
      return false
    ensure
      puts "[UnrealDB] Lock liberado: replace_all_files_local."
      @process_lock.unlock
    end
  end

  def receive_replicated_db(node_id, zip, conn)
    #puts "[UnrealDB] Antes do lock do receive_replicated_db."
    @process_lock.lock
    #puts "[UnrealDB] Depois do lock do receive_replicated_db."

    begin
      found_db = nil

      @replicated_dbs.each { |db| found_db = db if db.node_id == node_id }

      if found_db != nil
        if found_db.associated_conn != conn
          if conn != nil
            puts "[UnrealDB] Recebendo replica ja existente e de conexao diferente!!!"
          else
            puts "[UnrealDB] Setando nova conexao para banco ja replicado."
            found_db.associated_conn = conn
          end
        else
          puts "[UnrealDB] Recebendo replica ja existente da mesma conexao: " + get_short_node_id(node_id)
        end
        return found_db.replace_all_files(zip)
      end

      new_db = PersistentDB.new(node_id, "replicas/")
      new_db.associated_conn = conn

      @replicated_dbs << new_db

      return new_db.replace_all_files(zip)
    ensure
      #puts "[UnrealDB] Lock liberado: receive_replicated_db."
      @process_lock.unlock
    end
  end

  def put_files_local(node_id, zip)
    files_to_replicate = []

    #puts "[UnrealDB] Antes do lock do put_files_local."
    @process_lock.lock
    #puts "[UnrealDB] Depois do lock do put_files_local."

    begin
      found_db = nil

      @dbs.each { |db| found_db = db if db.node_id == node_id }

      if found_db != nil
        return found_db.insert_files(zip) do |file_id, content|
          files_to_replicate << [file_id, content]          
        end
      else
        puts "[UnrealDB] Banco local para inserir arquivos nao encontrado: " + get_short_node_id(node_id)
        return false
      end
    ensure
      #puts "[UnrealDB] Lock liberado: put_files_local."
      @process_lock.unlock
    end

    files_to_replicate.each do |id, raw|
      @replication_connections.each do |conn|
        conn.insert_document(id, raw, node_id)
      end
    end

    check_move_necessary(node_id)
  end

  def destroy_replica(node_id)
    #puts "[UnrealDB] Antes do lock do destroy_replica."
    @process_lock.lock
    #puts "[UnrealDB] Depois do lock do destroy_replica."

    begin
      dbs_to_remove = []

      @replicated_dbs.each do |db|
        if db.node_id == node_id
          db.destroy
          dbs_to_remove << db
          puts "[UnrealDB] Removendo banco replicado: " + get_short_node_id(db.node_id)
        end
      end

      dbs_to_remove.each { |removed_db| @replicated_dbs.delete(removed_db) }
    ensure
      #puts "[UnrealDB] Lock liberado: destroy_replica."
      @process_lock.unlock
    end
  end

  def conn_closed(conn)
    #puts "[UnrealDB] Antes do lock do conn_closed."
    @process_lock.lock
    #puts "[UnrealDB] Depois do lock do conn_closed."

    begin
      @replicated_dbs.each do |db|
        if db.associated_conn == conn
          db.associated_conn = nil
          db.abandoned_time = Time.now
          puts "[UnrealDB] Abondonando banco por conexao fechada: " + get_short_node_id(db.node_id)
        end
      end
    ensure
      #puts "[UnrealDB] Lock liberado: conn_closed."
      @process_lock.unlock
    end

    clean_abandoned_dbs
  end

  def get(key)
    node = @chord.lookup(key)

    return nil if node == nil

    client = DocumentTransferClient.new(node.host, HOST)

    return nil if !client.connect

    begin
      return client.get_document(Key.new(key).to_s, node.node_id.key.to_s)
    ensure
      client.close
    end
  end

  def put(document, key)
    node = @chord.lookup(key)

    return false if node == nil

    client = DocumentTransferClient.new(node.host, HOST)

    return false if !client.connect

    begin
      return client.insert_document(Key.new(key).to_s, document, node.node_id.key.to_s)
    ensure
      client.close
    end
  end

  def update(document, key)
    node = @chord.lookup(key)

    return false if node == nil

    client = DocumentTransferClient.new(node.host, HOST)

    return false if !client.connect

    begin
      return client.update_document(Key.new(key).to_s, document, node.node_id.key.to_s)
    ensure
      client.close
    end
  end

  def remove(key)
    node = @chord.lookup(key)

    return false if node == nil

    client = DocumentTransferClient.new(node.host, HOST)

    return false if !client.connect

    begin
      return client.remove_document(Key.new(key).to_s, node.node_id.key.to_s)
    ensure
      client.close
    end
  end

  def print_nodes
    @chord.print_nodes
  end

private
  def get_short_node_id(node_id)
    return node_id[0..4] + "..." + node_id[-4..-1]
  end

  def pre_internal_init
    begin
      @transfers_server.listen
      @api_listener.listen
      @chord.succs_added() { |node, new_succ, first| succs_added(node, new_succ, first) }
      @chord.succs_leaved() { |node, leaved, first| succs_leaved(node, leaved, first) }
      @chord.new_predecessor() { |node_id, pred_id, pred_host| new_pred(node_id, pred_id, pred_host) }
    rescue Exception => e
      puts "[UnrealDB] Excecao ao iniciar o banco: " + e.message
    end
  end

  def internal_init
    begin
      @chord.each_vnode { |i| @dbs << PersistentDB.new(i.node_id.key.to_s, "local/") }
      puts "[UnrealDB] Banco iniciado."
    rescue Exception => e
      puts "[UnrealDB] Excecao ao iniciar o banco: " + e.message
    end
  end

  # Determina se esse no tem alguma chave que nao pertence a ele.
  # Se tiver move para o predecessor.
  def check_move_necessary(node_id)
    puts "[UnrealDB] Iniciando checagem para determinar documentos fora da faixa: " + get_short_node_id(node_id)

    transfers_keys = false
    pred_id = nil
    pred_host = nil

    @replication_pred_lock.lock
    begin
      pred_id = @pred_id[node_id]
      pred_host = @pred_host[node_id]

      pred_key = Key.new("")
      pred_key.set_key_by_string(pred_id)
      node_key = Key.new("")
      node_key.set_key_by_string(node_id)

      db = nil

      @process_lock.lock
      begin
        @dbs.each { |each_db| db = each_db if each_db.node_id == node_id }

        if db == nil
          puts "[UnrealDB] Banco local nao encontrado: " + node_id
          return
        end

        qtd = db.qtd_files_range(pred_key, node_key, false, true, false)
      ensure
        @process_lock.unlock
      end

      if qtd > 0
        puts "[UnrealDB] Existem arquivos fora da faixa aqui: " + get_short_node_id(node_id)
        transfers_keys = true
      else
        puts "[UnrealDB] Nao existem arquivos fora da faixa aqui: " + get_short_node_id(node_id)
      end

    rescue Exception => e
      puts "[UnrealDB] Excecao em new_pred: " + e.message
    ensure
      @replication_pred_lock.unlock
    end

    local_db = false

    @chord.each_vnode do |vnode|
      if vnode.node_id.key.to_s == pred_id
        local_db = true
      end
    end

    if local_db
      move_files_local(node_id, pred_id, pred_host) if transfers_keys
    else
      move_files(node_id, pred_id, pred_host) if transfers_keys
    end
  end

  def clean_abandoned_dbs
    thread = Thread.new do
      sleep((60 * NUM_SUCCESSORS) + 5)

      #puts "[UnrealDB] Antes do lock do clean_abandoned_dbs."
      @process_lock.lock
      #puts "[UnrealDB] Depois do lock do clean_abandoned_dbs."
      begin
        dbs_to_remove = []

        @replicated_dbs.each do |db|          
          if db.associated_conn == nil
            diff = Time.now - db.abandoned_time
            if diff.to_i >= (60 * NUM_SUCCESSORS)
              db.destroy
              dbs_to_remove << db
              puts "[UnrealDB] Destruindo banco replicado abandonado " + get_short_node_id(db.node_id)
            end
          end
        end

        dbs_to_remove.each { |removed_db| @replicated_dbs.delete(removed_db) }
      rescue Exception => e
        puts "\n"
        puts e.backtrace.join("\n")
        puts "[UnrealDB] Excecao ao deletar bancos replicados abandonados: " + e.message
      ensure
        #puts "[UnrealDB] Lock liberado: clean_abandoned_dbs."
        @process_lock.unlock
      end

      thread.exit
    end
  end

  def new_pred(node_id, pred_id, pred_host)
    #puts "[UnrealDB] New pred: " + pred_id + "@" + pred_host

    local_db = false

    @chord.each_vnode do |vnode|
      if vnode.node_id.key.to_s == pred_id
        local_db = true
      end
    end

    transfers_keys = false
    use_replica = false

    @replication_pred_lock.lock
    begin
      if @pred_id[node_id] == nil
        @pred_host[node_id] = pred_host
        @pred_id[node_id] = pred_id
        return
      end

      curr_pred_key = Key.new("")
      pred_key = Key.new("")
      node_key = Key.new("")

      curr_pred_key.set_key_by_string(@pred_id[node_id])
      pred_key.set_key_by_string(pred_id)
      node_key.set_key_by_string(node_id)

      if pred_key.is_in_range(curr_pred_key, node_key)
        #puts "[UnrealDB] Novo no adicionado."
        transfers_keys = true
      else
        #puts "[UnrealDB] No predecessor caiu ou saiu."
        use_replica = true
      end

      @pred_host[node_id] = pred_host
      @pred_id[node_id] = pred_id
    rescue Exception => e
      puts "[UnrealDB] Excecao em new_pred: " + e.message
    ensure
      @replication_pred_lock.unlock
    end

    if local_db
      move_files_local(node_id, pred_id, pred_host) if transfers_keys
    else
      move_files(node_id, pred_id, pred_host) if transfers_keys     
    end

    apply_replica(node_id, pred_id) if use_replica
  end

  def apply_replica(node_id, pred_id)
    thread = Thread.new do
      sleep(2 * NUM_VNODES)

      puts "[UnrealDB] Iniciando aplicacao de replica: " + get_short_node_id(pred_id) + " ate " + get_short_node_id(node_id)

      @replication_pred_lock.lock
      begin
        if pred_id != @pred_id[node_id]
          puts "[UnrealDB] Cancelada a aplicacao da replica. Pred mudou: " + get_short_node_id(pred_id)
          thread.exit
        end

        # Necessario pegar o lock de processamento tambem pois para mexer nos bancos replicados
        # temos que pegar esse lock.
        #puts "[UnrealDB] Antes do lock do apply_replica."
        @process_lock.lock
        #puts "[UnrealDB] Depois do lock do apply_replica."
        begin
          local_db = nil

          # Procura banco local que sera incrementado pela replicacao.
          @dbs.each do |db|
            local_db = db if db.node_id == node_id
          end

          if local_db == nil
            puts "[UnrealDB] Erro ao procurar banco local a ser incrementado pela replicacao: " + get_short_node_id(node_id)
            thead.exit
          end

          pred_key = Key.new("")
          node_key = Key.new("")

          pred_key.set_key_by_string(pred_id)
          node_key.set_key_by_string(node_id)

          @replicated_dbs.each do |db|
            db_key = Key.new("")
            db_key.set_key_by_string(db.node_id)

            if db_key.is_in_range(pred_key, node_key, false, true)
              file = db.get_all_files_range(pred_key, node_key, false, true, true)

              if file == nil
                puts "[UnrealDB] Erro obtendo arquivos de banco replicado: " + get_short_node_id(pred_id)
                thread.exit
              end

              if !local_db.insert_files(file) do |file_id, content|
                  @replication_connections.each { |conn| conn.insert_document(file_id, content, node_id) }
                end
                puts "[UnrealDB] Erro ao aplicar arquivos de banco relicado: " + get_short_node_id(pred_id)
                thread.exit
              end

              puts "[UnrealDB] Aplicada replica: " + get_short_node_id(db.node_id) + " => " + get_short_node_id(node_id)
            end
          end
        ensure
          #puts "[UnrealDB] Lock liberado: apply_replica."
          @process_lock.unlock
        end
      rescue Exception => e
        puts "\n"
        puts e.backtrace.join("\n")
        puts "[UnrealDB] Excecao ao aplicar replica: " + e.message
      ensure
        @replication_pred_lock.unlock
      end

      thread.exit
    end
  end

  def move_files_local(node_id, pred_id, pred_host)
    thread = Thread.new do
      sleep(2 * NUM_VNODES)

      puts "[UnrealDB] Iniciando a operacao de mover arquivos locais."

      @replication_pred_lock.lock
      begin
        if pred_id != @pred_id[node_id]
          puts "[UnrealDB] Cancelada a operacao de mover arquivos locais. Pred mudou: " + get_short_node_id(pred_id)
          thread.exit
        end

        puts "[UnrealDB] Movendo arquivos locais: " + get_short_node_id(node_id) + " => " + get_short_node_id(pred_id)

        # Para mexer nos bancos locais tem que dar lock de processamento.
        #puts "[UnrealDB] Antes do lock do move_files_local."
        @process_lock.lock
        #puts "[UnrealDB] Depois do lock do move_files_local."
        begin
          src_db = nil
          dst_db = nil

          # Procura banco local de origem e destino.
          @dbs.each do |db|
            src_db = db if db.node_id == node_id
            dst_db = db if db.node_id == pred_id
          end

          if src_db == nil || dst_db == nil
            puts "[UnrealDB] Erro ao procurar banco local de origem ou destino."
            thread.exit
          end

          pred_key = Key.new("")
          node_key = Key.new("")

          pred_key.set_key_by_string(pred_id)
          node_key.set_key_by_string(node_id)

          zip_file = src_db.get_all_files_range(pred_key, node_key, true, false, false)

          if zip_file == nil
            puts "[UnrealDB] Erro ao zipar banco para mover."
            thread.exit
          end

          if !dst_db.insert_files(zip_file) do |file_id, content|
              @replication_connections.each { |conn| conn.insert_document(file_id, content, pred_id) }
            end
            puts "[UnrealDB] Erro ao mover arquivos zipados locais."
            thread.exit
          end

          if !src_db.remove_all_files_range(pred_key, node_key, true, false, false) do |file_id|
              @replication_connections.each { |conn| conn.remove_document(file_id, node_id) }
            end
            puts "[UnrealDB] Erro ao excluir arquivos movidos locais."
          end
        ensure
          #puts "[UnrealDB] Lock liberado: move_files_local."
          @process_lock.unlock
        end
      rescue Exception => e
        puts "\n"
        puts e.backtrace.join("\n")
        puts "[UnrealDB] Excecao ao mover arquivos locais: " + e.message
      ensure
        @replication_pred_lock.unlock
      end

      check_move_necessary(pred_id)

      thread.exit
    end
  end

  def move_files(node_id, pred_id, pred_host)
    thread = Thread.new do
      sleep(2 * NUM_VNODES)

      puts "[UnrealDB] Iniciando a operacao de mover arquivos: " + get_short_node_id(pred_id)

      @replication_pred_lock.lock
      begin
        if pred_id != @pred_id[node_id]
          puts "[UnrealDB] Cancelada a operacao de mover arquivos. Pred mudou: " + get_short_node_id(pred_id)
          thread.exit
        end

        5.times do
          puts "[UnrealDB] Movendo arquivos: " + get_short_node_id(node_id) + " => " + get_short_node_id(pred_id)

          conn = DocumentTransferClient.new(pred_host, HOST)

          if !conn.connect
            puts "[UnrealDB] Erro ao conectar no host para mover arquivos: " + get_short_node_id(pred_id) + "@" + pred_host
            next
          end

          # Para mexer nos bancos locais tem que dar lock de processamento.
          #puts "[UnrealDB] Antes do lock do move_files."
          @process_lock.lock
          #puts "[UnrealDB] Depois do lock do move_files."
          begin
            local_db = nil

            # Procura banco local.
            @dbs.each do |db|
              local_db = db if db.node_id == node_id
            end

            if local_db == nil
              puts "[UnrealDB] Erro ao procurar banco local a ser movido!!"
              next
            end

            pred_key = Key.new("")
            node_key = Key.new("")

            pred_key.set_key_by_string(pred_id)
            node_key.set_key_by_string(node_id)

            zip_file = local_db.get_all_files_range(pred_key, node_key, true, false, false)

            if zip_file == nil
              puts "[UnrealDB] Erro ao zipar banco para mover."
              next
            end            
          ensure
            #puts "[UnrealDB] Lock liberado: move_files."
            @process_lock.unlock
          end

          if !conn.insert_documents(zip_file, pred_id)
            puts "[UnrealDB] Erro ao mover arquivos zipados."
            next
          end

          files_ids = []

          # Para mexer nos bancos locais tem que dar lock de processamento.
          #puts "[UnrealDB] Antes do lock do move_files 2."
          @process_lock.lock
          #puts "[UnrealDB] Depois do lock do move_files 2."
          begin
            if !local_db.remove_all_files_range(pred_key, node_key, true, false, false) do |file_id|
                files_ids << file_id
              end
              puts "[UnrealDB] Erro ao excluir arquivos movidos."
            end
          ensure
            #puts "[UnrealDB] Lock liberado: move_files 2."
            @process_lock.unlock
          end

          files_ids.each do |id|
            @replication_connections.each { |conn| conn.remove_document(id, node_id) }
          end

          thread.exit
        end
      rescue Exception => e
        puts "\n"
        puts e.backtrace.join("\n")
        puts "[UnrealDB] Excecao ao mover arquivos: " + e.message
      ensure
        @replication_pred_lock.unlock
      end

      thread.exit
    end
  end

  def succs_added(node_id, succ, first)
    succ_id = succ[0]
    succ_host = succ[1]

    #begin
    #  puts "[UnrealDB] succs_added: " + node_id + " => " + succ_id
    #rescue Exception => e
    #  puts "[UnrealDB] Excecao ao printar entrada de um no: " + e.message
    #end

    # Temos que checar se esse sucessor nao e' um virtual node,
    # pois se for nao precisa replicar para ele.
    @chord.each_vnode do |vnode|
      if vnode.node_id.key.to_s == succ_id
        #puts "[UnrealDB] Succs_added = vnode"
        return
      end
    end

    @replication_succ_lock.lock
    begin      
      conn = nil

      @replication_connections.each do |iter|
        if iter.host == succ_host
          conn = iter
        end
      end

      if conn == nil
        new_conn = DocumentTransferClient.new(succ_host, HOST)
        if !new_conn.connect
          puts "[UnrealDB] Erro conectando ao sucessor para replicar."
          return
        end

        new_conn.start_node_id_relation
        @replication_connections << new_conn
        conn = new_conn
      end

      if conn.contain_node_id(node_id)
        transfer = false
      else
        transfer = true
      end

      if conn.add_node_id_relation(node_id, succ_id)
        #puts "[UnrealDB] Adicionada relacao: " + get_short_node_id(node_id) + " => " + get_short_node_id(succ_id)
      end
    rescue Exception => e
      puts "\n"
      puts e.backtrace.join("\n")
      puts "[UnrealDB] Excecao succs_added: " + e.message
    ensure
      @replication_succ_lock.unlock
    end
    
    replicate_documents_to_node(node_id, succ_id, succ_host) if transfer
  end

  def replicate_documents_to_node(node_id, succ_id, succ_host)
    # Cria uma Thread que vai enviar os documentos para esse no'
    thread = Thread.new do
      sleep(2 * NUM_VNODES)

      @replication_succ_lock.lock
      begin
        conn = nil
        @replication_connections.each do |iter|
          if iter.host == succ_host
            conn = iter
          end
        end
        if conn == nil
          puts "[UnrealDB] O no nao e' mais um sucessor. A replicacao foi cancelado: " + get_short_node_id(succ_id)
          thread.exit
        end
        if !conn.contain_node_id(node_id)
          puts "[UnrealDB] O no nao e' mais um sucessor = node_id. A replicacao foi cancelado: " + get_short_node_id(node_id)
          thread.exit
        end
      rescue Exception => e
        puts "\n"
        puts e.backtrace.join("\n")
        puts "[UnrealDB] Excecao ao replicar banco: " + e.message
      ensure
        @replication_succ_lock.unlock
      end

      5.times do
        begin
          puts "[UnrealDB] Replicando: " + get_short_node_id(node_id) + " => " + get_short_node_id(succ_id)

          # Para mexer nos bancos locais tem que dar lock de processamento.
          #puts "[UnrealDB] Antes do lock do replicate_documents_to_node."
          @process_lock.lock
          #puts "[UnrealDB] Depois do lock do replicate_documents_to_node."
          begin
            local_db = nil

            # Procura banco local do no que sera transferido para o novo sucessor
            @dbs.each do |db|
              local_db = db if db.node_id == node_id
            end

            if local_db == nil
              puts "[UnrealDB] Erro ao procurar banco local a ser replicado!!"
              next
            end

            zip_file = local_db.get_all_files
          ensure
            #puts "[UnrealDB] Lock liberado: replicate_documents_to_node."
            @process_lock.unlock
          end

          if zip_file == nil
            puts "[UnrealDB] Erro ao zipar banco para envio."
            next
          end

          if !conn.replicate_documents(zip_file, node_id)
            puts "[UnrealDB] Erro ao transferir documentos zipados."
            next
          end
        rescue Exception => e
          puts "\n"
          puts e.backtrace.join("\n")
          puts "[UnrealDB] Erro replicando documentos: " + e.message
        end

        thread.exit
      end
    end
  end

  def succs_leaved(node_id, leaved, first)
    leaved_id = leaved[0]
    leaved_host = leaved[1]

    #puts "[UnrealDB] Leaved: " + node_id + " => " + leaved_id

    @chord.each_vnode do |vnode|
      if vnode.node_id.key.to_s == leaved_id
        #puts "[UnrealDB] Succs_added = vnode"
        return
      end
    end

    @replication_succ_lock.lock
    begin
      conn = nil

      @replication_connections.each { |iter| conn = iter if iter.host == leaved_host }

      if conn
        #puts "[UnrealDB] Saindo succ: " + get_short_node_id(node_id) + " => " + get_short_node_id(leaved_id)
        if !conn.contain_node_id_relation(node_id, leaved_id)
          #puts "[UnrealDB] Conexao nao contem o node id que esta saindo..."
          return
        end

        conn.remove_node_id_relation(node_id, leaved_id)

        if conn.qtd_node_id <= 0
          conn.close
          @replication_connections.delete_if { |conn| conn.host == leaved_host }
          puts "[UnrealDB] Conexao de replicacao removida: " + leaved_host
        else
          if conn.qtd_node_id_relation(node_id) <= 0
            if conn.destroy_replica(node_id)
              #puts "[UnrealDB] Sucesso ao destruir replica associada a uma conexao: " + get_short_node_id(node_id)
            else
              #puts "[UnrealDB] Erro ao destruir replica associada a uma conexao: " + get_short_node_id(node_id)
            end
          else
            #puts "[UnrealDB] Nao era a ultima relacao paro o no, sua replica sera mantida: " + get_short_node_id(node_id)
          end
        end        
      else
        #puts "[UnrealDB] Nao foi encontrada a conexao associado ao sucessor que saiu!!"
      end
    ensure
      @replication_succ_lock.unlock
    end
  end
end
